//
// Created by tang on 22-11-17.
//

#include "fpga_control_app.hpp"
#include "imgui/memory_editor.hpp"

#include <glob.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

constexpr int MAX_BAR_NUM = 6;

struct UioDev {
    bool Initialize(uint16_t vid, uint16_t did);
    void Cleanup();
    void LoadConfigMemery();

    int       uio_fd{};
    int       config_fd{};
    void     *bars[MAX_BAR_NUM]{};
    uint64_t  bar_length[MAX_BAR_NUM]{};
    char      config_buff[4<<10]{};
};

static std::vector<std::string> get_uio_devices()
{
    std::vector<std::string> result;

    glob_t glob_buff{};
    glob("/dev/uio*", 0, nullptr, &glob_buff);

    if (glob_buff.gl_pathc > 0) {
        for (decltype(glob_buff.gl_pathc) i = 0; i<glob_buff.gl_pathc; ++i) {
            LOG_D("Matched %s", glob_buff.gl_pathv[i]);
            result.emplace_back(std::string{glob_buff.gl_pathv[i]+5}); // len("/dev/")
        }
    }

    globfree(&glob_buff);

    return result;
}

static std::pair<uint16_t, uint16_t> get_pci_info(const std::string &uio_id)
{
    std::pair<uint16_t, uint16_t> result = {0,0};

    char path_buff[256] {0};
    snprintf(path_buff, sizeof(path_buff), "/sys/class/uio/%s/device/config", uio_id.c_str());
    int fd = open(path_buff, O_RDONLY);
    if (fd > 0) {
        read(fd, &result, sizeof (result));
    }

    return result;
}

static std::vector<uint64_t> get_uio_map_infos(const std::string &uio_id) {
    std::vector<uint64_t> result;

    glob_t glob_buff{};
    char path_buff[256] {0};
    snprintf(path_buff, sizeof(path_buff), "/sys/class/uio/%s/maps/map*", uio_id.c_str());
    glob(path_buff, 0, nullptr, &glob_buff);
    if (glob_buff.gl_pathc > 0)
    {
        for (decltype(glob_buff.gl_pathc) i = 0; i<glob_buff.gl_pathc; ++i)
        {
            uint64_t item = 0;
            snprintf(path_buff, sizeof(path_buff), "%s/size", glob_buff.gl_pathv[i]);
            FILE* f = fopen(path_buff, "r");
            fscanf(f, "%18lx", &item);
            fclose(f);

            result.emplace_back(item);
        }
    }
    return result;
}

static UioDev *dev = nullptr;

void FpgaControl::OnInitialize() {
    App::OnInitialize();

    constexpr uint16_t vendor_id = 0x10ee;
    constexpr uint16_t device_id = 0x7011;

    // uio init
    {
        dev = new UioDev{};
        memset(dev, 0x00, sizeof (UioDev));
        // search device
        bool const ret = dev->Initialize(vendor_id, device_id);
        assert (ret);
    }
}
void FpgaControl::OnCleanup() {
    App::OnCleanup();

    // clean up uio


    delete dev;
    dev = nullptr;
}

void FpgaControl::RenderFrame(double delta_time, double total_time) {
    // nothing to draw
}

void FpgaControl::RenderConfigUI() {
    // nothing to show
}

template <typename T>
static void GPIO(const char *name, T *mem, const T mask, bool inverse, bool readonly = false)
{
    bool on = (*mem & mask) == mask;
    if (inverse)
        on = !on;
    const float cursorX = ImGui::GetCursorPosX();
    ImGui::BulletText("%s", name);
    ImGui::SameLine(cursorX + ImGui::GetFontSize() * 16.f);
    ImGui::BeginDisabled(readonly);
    ImGui::PushID(name);
    if (ImGui::Checkbox("##VALUE", &on))
    {
        if (on) {
            if (inverse)
                *mem &= ~mask;
            else
                *mem |= mask;
        } else {
            if (inverse)
                *mem |= mask;
            else
                *mem &= ~mask;
        }
    }
    ImGui::PopID();
    ImGui::EndDisabled();
}

void FpgaControl::StandaloneUI() {
    using namespace ImGui;
//    SetNextWindowPos(ImVec2(0,0), ImGuiCond_Always);
//    SetNextWindowSize(ImVec2(m_window_width_, m_window_height_), ImGuiCond_Always);
    if (Begin("FPGA Control", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
    {
        AlignTextToFramePadding();
        GPIO("IRQ Enable", ((uint32_t*)dev->bars[0])+1, 0x1u, false);
        GPIO("IRQ Clear", ((uint32_t*)dev->bars[0])+2, 0x1u, false);
        GPIO("IRQ Ack", ((uint32_t*)dev->bars[0])+3, 0x1u, false);
        GPIO("IRQ MSI MODE", ((uint32_t*)dev->bars[0])+4, 0x1u, false);

        GPIO("LED 0", ((uint32_t*)dev->bars[0])+5, 0x1u, true);
        //GPIO("LED 1", ((uint32_t*)dev->bars[0])+5, 0x2u, true);


        GPIO("Irq_req", ((uint32_t*)dev->bars[0])+8, 0x1u, false,true);
        GPIO("Irq_ack", ((uint32_t*)dev->bars[0])+9, 0x1u, false,true);
        GPIO("Irq_ready", ((uint32_t*)dev->bars[0])+10, 0x1u, false,true);

        InputInt("Timer Value", (int*)dev->bars[0], 1, 100);
        InputInt("Register Value", (int*)dev->bars[0] + 7, 1, 100);
        if (Button("Load Config"))
            dev->LoadConfigMemery();

        Separator();

#define GPIO_REG32(bar,pos) \
        GPIO(#pos, (uint32_t*)dev->bars[bar]+pos/4+0x800, 1u, false)

        GPIO_REG32(1,0x04);
        GPIO_REG32(1,0x08);
        GPIO_REG32(1,0x0c);
        GPIO_REG32(1,0x10);
        GPIO_REG32(1,0x14);
        GPIO_REG32(1,0x18);
        GPIO_REG32(1,0x40);
        GPIO_REG32(1,0x44);
        GPIO_REG32(1,0x48);
        GPIO_REG32(1,0x4c);
        GPIO_REG32(1,0x80);
        GPIO_REG32(1,0x84);
        GPIO_REG32(1,0x88);
        GPIO_REG32(1,0x8c);

#undef GPIO_REG32
    }
    End();
    
//    static MemoryEditor s_cfg; s_cfg.ReadOnly = true;
//    s_cfg.DrawWindow("Config", dev->config_buff, sizeof(dev->config_buff));
    static MemoryEditor s_bar0;
    s_bar0.DrawWindow("Bar 0 @0x00",dev->bars[0], 128);
    static MemoryEditor s_bar1; s_bar1.Cols = 32;
    s_bar1.DrawWindow("Bar 1",(uint8_t*)dev->bars[1], 65536);
}

uint32_t FpgaControl::UIFlags() const {
    return App::UIFlags() & ~UF_CONFIG;
}

bool UioDev::Initialize(uint16_t vid, uint16_t did) {
    auto uio_files = get_uio_devices();
    auto target = std::find_if(begin(uio_files), end(uio_files), [vid,did](const std::string &uio_id)->bool{
        auto const pair = get_pci_info(uio_id);
        return pair.first==vid && pair.second == did;
    });
    if (target != end(uio_files)) {
        LOG_I("Found Device(%04x:%04x) at /dev/%s", vid, did, target->c_str());

        char path_buff[256] {0};
        snprintf(path_buff, sizeof(path_buff), "/dev/%s", target->c_str());
        uio_fd = open(path_buff, O_SYNC | O_RDWR);
        if (uio_fd < 0) {
            LOG_F("Could not open file: %s", path_buff);
        }
        
        snprintf(path_buff, sizeof(path_buff), "/sys/class/uio/%s/device/config", target->c_str());
        config_fd = open(path_buff, O_RDONLY);
        if (config_fd > 0) {
            LoadConfigMemery();
        } else {
            LOG_F("Could not open file: %s", path_buff);
        }

        auto const ranges = get_uio_map_infos(*target);
        if (ranges.empty()) {
            LOG_I("Device have no bar(s)!!");
        } else {
            uint64_t offset = 0;
            for (int i=0; i<std::min<int>(MAX_BAR_NUM, (int)ranges.size()); ++i) {
                bars[i] = mmap(nullptr, ranges[i], PROT_READ | PROT_WRITE, MAP_SHARED, uio_fd, i*getpagesize());
                if (bars[i] == (void*)-1)
                {
                    LOG_F("Map Failed: %s", strerror(errno));
                }
//                offset += ((bar_length[i] = ranges[i])-1);
                LOG_I("Bar %d, size %u, addr %p", i, ranges[i], bars[i]);
            }
            return true;
        }
    } else {
        LOG_F("Target Device(%04x:%04x) Not Found!", vid, did);
    }
    return false;
}
void UioDev::Cleanup() {
    if (uio_fd > 0) {
        for (int i=0; i<MAX_BAR_NUM; ++i) {
            if (bars[i] != nullptr) {
                munmap(bars[i], bar_length[i]);
                bars[i] = nullptr;
                bar_length[i] = 0;
            }
        }
        close(uio_fd);
    }
    uio_fd = 0;
    
    if (config_fd > 0) {
        close(config_fd);
    }
    config_fd = 0;
}

void UioDev::LoadConfigMemery()
{
    lseek(config_fd, SEEK_SET, 0);
    const ssize_t len = read(config_fd, config_buff, sizeof(config_buff));
    LOG_I("Config Read %u", (uint32_t)len);
}

DEFINE_FACTORY_FUNC(CreateFpgaControlApp, win, args) {
    (void) args;
    return std::make_unique<FpgaControl>(win, "FPGA Control");
}

REGISTER_APP_CREATOR("FpgaControl", &CreateFpgaControlApp);
